package m.appserver;

import com.alibaba.fastjson.JSONObject;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.DefaultFileRegion;
import io.netty.handler.codec.http.*;
import io.netty.handler.codec.http.cookie.ClientCookieEncoder;
import io.netty.handler.codec.http.cookie.Cookie;
import io.netty.util.AsciiString;
import m.common.action.ActionResultType;
import m.common.action.HtmlResult;
import m.common.action.VueResult;
import m.system.RuntimeData;
import m.system.exception.MException;
import m.system.lang.HtmlBodyContent;
import m.system.url.UrlMarker;
import m.system.util.*;
import manage.util.excel.ExcelObject;

import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.*;
import java.lang.reflect.InvocationTargetException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;

public class ResponseUtil {
	protected ResponseUtil(ChannelHandlerContext ctx,FullHttpRequest request,RequestUtil reqUtil) {
		this.ctx=ctx;
		this.request=request;
		response=new DefaultHttpResponse(HttpVersion.HTTP_1_1,HttpResponseStatus.OK);
		addCommonHeader();
		this.reqUtil=reqUtil;
	}
	private FullHttpResponse getFullResponse(String txt) {
		FullHttpResponse resp=new DefaultFullHttpResponse(HttpVersion.HTTP_1_1,HttpResponseStatus.OK,Unpooled.wrappedBuffer(txt.getBytes(StandardCharsets.UTF_8)));
		resp.headers().add(response.headers());
		return resp;
	}
	private final HttpResponse response;
	private final ChannelHandlerContext ctx;
	private final FullHttpRequest request;
	private final RequestUtil reqUtil;
	private String contentType;
	private RandomAccessFile raf;
	public void setHeader(String name,Object value) {
		this.response.headers().set(AsciiString.cached(name), value);
	}
	public HttpHeaders headers() {
		return this.response.headers();
	}

	/**
	 * 手动设置返回的内容类型
	 */
	public void setContentType(String contentType) {
		this.contentType = contentType;
	}

	private String getContentType(String suffix) {
		if(!StringUtil.isSpace(this.contentType)){
			return this.contentType;
		}
		suffix=suffix.toUpperCase();
		switch (suffix) {
			case "TXT":
				return "text/plain";
			case "CSS":
				return "text/css";
			case "HTML":
				return "text/html";
			case "JS":
				return "application/javascript";
			case "JSON":
				return "application/json";
			case "PNG":
				return "image/png";
			case "JPG":
				return "image/jpeg";
			case "GIF":
				return "image/gif";
			case "XLSX":
				return "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
			case "XLS":
				return "application/vnd.ms-excel";
			default:
				return "*/*";
		}
	}

	/**
	 * 添加公共的header属性 urlmarker.xml配置
	 */
	private void addCommonHeader(){
		Map<String,String> map=UrlMarker.getResponseHeaderMap();
		for(String k : map.keySet()){
			setHeader(k,map.get(k));
		}
	}

	private String toSetCookie() {
		Set<Cookie> cookies = reqUtil.getCookies();
		for(Cookie ck : cookies) {
			if(ck.name().equals(AuthInfoUtil.SESSION_NAME)) {
				ck.setPath("/");
				return ClientCookieEncoder.STRICT.encode(ck)+";path=/;HttpOnly=true";
			}
		}
		return "";
	}
//	public void redirectFilter(String redirectPath,JSONObject requestBody,Map<String,File> fileMap) throws UnsupportedEncodingException {
//		FullHttpResponse res=getFullResponse("");
//		res.setStatus(HttpResponseStatus.PERMANENT_REDIRECT); //设置重定向响应码 （临时重定向、永久重定向）
//		HttpHeaders headers = res.headers();
//		headers.set(HttpHeaderNames.ACCESS_CONTROL_ALLOW_HEADERS, "x-requested-with,content-type");
//		headers.set(HttpHeaderNames.ACCESS_CONTROL_ALLOW_METHODS, "POST,GET");
//		headers.set(HttpHeaderNames.ACCESS_CONTROL_ALLOW_ORIGIN, "*");
//		headers.set(HttpHeaderNames.CONTENT_LENGTH, res.content().readableBytes());
//		headers.set(HttpHeaderNames.LOCATION, redirectPath); //重定向URL设置
//		ctx.writeAndFlush(res).addListener(ChannelFutureListener.CLOSE);
//	}

	/**
	 * 重定向执行Action
	 */
	public Object redirect(ActionRedirect ar) {
		try {
			//执行action的方法-----------------------------------
			Object result = ar.toResult(reqUtil,this);
			if(null==result) {
				throw new MException(this.getClass(),"result:<null>");
			}else if (result instanceof ActionRedirect) {//重定向类
				if(null!=ar.getRealAction()) ar.getRealAction().endLog(ServerResultStatus.REDIRECT,result.toString());
				result = redirect((ActionRedirect) result);
			}else{
				if(null!=ar.getRealAction()) ar.getRealAction().endLog(ServerResultStatus.SUCCESS,result.toString());
			}
			return result;
		}catch (Exception e){
			SystemUtil.println("Response redirect:",e.getMessage());
			if(RuntimeData.getDebug()&&!RuntimeData.getOutLog()){
				e.printStackTrace();
			}
			String error;
			MException me=null;
			if(e instanceof InvocationTargetException){
				Throwable target=((InvocationTargetException)e).getTargetException();
				if(target instanceof MException) me=(MException) target;
				error=target.getMessage();
			}else{
				if(e instanceof MException) me=(MException) e;
				error=e.getMessage();
			}
			ar.getRealAction().mustLog("异常日志","未记录的异常日志");
			if(null!=me) {
				me.record();
				ActionRedirect redirect=me.getRedirect();
				if(null!=redirect){
					JSONMessage msg=new JSONMessage();
					msg.push("error",error);
					msg.push("msg","发生异常，异常设置了重定向。");
					msg.push("redirect",redirect.getContent());
					ar.getRealAction().endLog(ServerResultStatus.REDIRECT,msg.toJSONString());
					return redirect(redirect);
				}
			}
			ActionResultType type = ar.toResultType();
			if(null!=type) {
				if(type==ActionResultType.Json){
					JSONMessage result=new JSONMessage()
					.setStatus(ServerResultStatus.EXCEPTION)
					.push("msg",error)
					.push("code",9);
					ar.getRealAction().endLog(ServerResultStatus.EXCEPTION,result.getContent());
					return result;
				}else if(type==ActionResultType.VuePage){
					VueResult result=VueResult.toExceptionPage(error,true);
					ar.getRealAction().endLog(ServerResultStatus.EXCEPTION,result.getContent());
					return result;
				}else if(type==ActionResultType.HtmlPage){
					HtmlResult result=HtmlResult.toErrorPage(error);
					ar.getRealAction().endLog(ServerResultStatus.EXCEPTION,result.getContent());
					return result;
				}else if(type==ActionResultType.BodyContent){
					HtmlBodyContent result=new HtmlBodyContent(error).setStatus(ServerResultStatus.EXCEPTION);
					ar.getRealAction().endLog(ServerResultStatus.EXCEPTION,result.getContent());
					return result;
				}
			}else{
				JSONMessage json=new JSONMessage();
				json.push("error",error);
				List<String> lines=new ArrayList<>();
				for(StackTraceElement ele : e.getStackTrace()){
					lines.add(ele.getClassName()+"."+ele.getMethodName()+" at "+ele.getLineNumber());
				}
				json.push("lines",lines);
				ar.getRealAction().endLog(ServerResultStatus.EXCEPTION,json.toJSONString());
			}
			return e;
		}
	}
	protected Object[] toMethodParams(Map<String,Class<?>> paramTypeMap, JSONObject requestBody) {
		List<Object> ls=new ArrayList<>();
		for(String key : paramTypeMap.keySet()) {
			Class<?> clazz=paramTypeMap.get(key);
			Object obj=null;
			if(requestBody != null) {
				obj=requestBody.get(key);
			}
			if(!clazz.equals(String.class)) {
				if(null!=obj&&StringUtil.isSpace(obj.toString())) {
					obj=null;
				}
			}
			ls.add(ObjectUtil.convert(clazz, obj));
		}
		return ls.toArray(new Object[] {});
	}

	/**
	 * 输出html   text/html
	 */
	public void outHtml(String html) throws UnsupportedEncodingException {
		outTxt(HttpResponseStatus.OK,html,"HTML");
	}

	/**
	 * 输出txt   text/plain
	 */
	public void outTxt(HttpResponseStatus status,String txt) throws UnsupportedEncodingException {
		outTxt(status,txt,"TXT");
	}
	/**
	 * 输出txt   自定义返回类型
	 */
	public void outTxt(HttpResponseStatus status,String txt,String suffix) {
		FullHttpResponse res=getFullResponse(txt);
		res.setStatus(status);
		HttpHeaders heads = res.headers();
		heads.add(HttpHeaderNames.CONTENT_TYPE, getContentType(suffix)+"; charset=UTF-8");
		heads.add(HttpHeaderNames.CONTENT_LENGTH, res.content().readableBytes()); // 3
		heads.add(HttpHeaderNames.CONNECTION, HttpHeaderValues.KEEP_ALIVE);
		heads.set(HttpHeaderNames.SET_COOKIE,toSetCookie());
		ctx.writeAndFlush(res).addListener(ChannelFutureListener.CLOSE);
	}

	/**
	 * 输出路径文件
	 */
	public void outPath(String path) throws UnsupportedEncodingException {
		String p= URLDecoder.decode(path,"UTF-8");
		File file;
		if(FileUtil.existsWebFile(p)) {//寻找web目录下文件
			file=new File(RuntimeData.getClassWeb()+p);
		}else if(FileUtil.existsPublicFile(p)) {//寻找公共web目录下文件
			file=new File(RuntimeData.getPublicWeb()+p);
		}else {//不存在则找临时目录
			String ps=RuntimeData.getClassTmp()+p;
			file=new File(ps);
			if(!file.exists()) {
				try {//临时目录不存在则从jar包内拷贝到临时目录
					InputStream is=this.getClass().getClassLoader().getResourceAsStream("web"+(p.indexOf("/")==0?"":"/")+p);
					if(null!=is) FileUtil.writeFile(ps, is);
				} catch (Exception e) {//不存在则404
					outTxt(HttpResponseStatus.NOT_FOUND, p+" Not Found");
					return;
				}
			}
		}
		//输出路径是请求路径的开头，且符合缓存url
		if(this.reqUtil.getUri().indexOf(path)==0&&UrlMarker.isCacheUrl(path)){
			response.headers().set(HttpHeaderNames.CACHE_CONTROL,"private, max-age=21600");//缓存6个小时
		}
		outFile(file,p.lastIndexOf(".")>=0?p.substring(p.lastIndexOf(".")+1):"");
	}

	/**
	 * 输出excel
	 */
	public void outExcel(ExcelObject excel) throws UnsupportedEncodingException {
		try {
			raf = new RandomAccessFile(excel.toExcelFile(), "r");
			long fileLength = raf.length();
			//定义response对象
			response.setStatus(HttpResponseStatus.OK);
			//设置请求头部
			response.headers().set(HttpHeaderNames.CONTENT_LENGTH, fileLength);
			response.headers().set(HttpHeaderNames.CONTENT_TYPE, getContentType("xlsx")+"; charset=UTF-8");
			response.headers().set(HttpHeaderNames.CONTENT_DISPOSITION, "attachment; filename="+URLEncoder.encode(excel.getName(),"UTF-8")+".xlsx");
			response.headers().set(HttpHeaderNames.SET_COOKIE,toSetCookie());
			ctx.write(response);
			ctx.write(new DefaultFileRegion(raf.getChannel(), 0, fileLength), ctx.newProgressivePromise());
			//刷新缓冲区数据，文件结束标志符
			ctx.writeAndFlush(LastHttpContent.EMPTY_LAST_CONTENT);
		} catch (Exception e) {
			e.printStackTrace();
			outTxt(HttpResponseStatus.NOT_FOUND,e.getMessage());
		}
	}

	/**
	 * 输出文件
	 */
	public void outFile(File file,String suffix) throws UnsupportedEncodingException {
		try {
			raf = new RandomAccessFile(file, "r");
			long fileLength = raf.length();
			//定义response对象
			response.setStatus(HttpResponseStatus.OK);
			//设置请求头部
			response.headers().set(HttpHeaderNames.CONTENT_LENGTH, fileLength);
			response.headers().set(HttpHeaderNames.CONTENT_TYPE, getContentType(suffix)+"; charset=UTF-8");
			response.headers().set(HttpHeaderNames.SET_COOKIE,toSetCookie());
			ctx.write(response);
			ctx.write(new DefaultFileRegion(raf.getChannel(), 0, fileLength), ctx.newProgressivePromise());
			//刷新缓冲区数据，文件结束标志符
			ctx.writeAndFlush(LastHttpContent.EMPTY_LAST_CONTENT);
		} catch (Exception e) {
			outTxt(HttpResponseStatus.NOT_FOUND,"no file!");
		}
	}

	/**
	 * 输出图片
	 */
	public void outBufferedImage(BufferedImage image,String suffix) throws UnsupportedEncodingException {
		try {
			ByteArrayOutputStream os=new ByteArrayOutputStream();
			ImageIO.write(image, suffix, os);
			byte[] bs=os.toByteArray();
			//定义response对象
			response.setStatus(HttpResponseStatus.OK);
			//设置请求头部
			response.headers().set(HttpHeaderNames.CONTENT_LENGTH, bs.length);
			response.headers().set(HttpHeaderNames.CONTENT_TYPE, getContentType(suffix)+"; charset=UTF-8");
			response.headers().set(HttpHeaderNames.SET_COOKIE,toSetCookie());
			ctx.write(response);
			ByteBuf bb=Unpooled.wrappedBuffer(bs);
			ctx.write(bb);
			//刷新缓冲区数据，文件结束标志符
			ctx.writeAndFlush(LastHttpContent.EMPTY_LAST_CONTENT);
			os.close();
			bb.release();
		} catch (Exception e) {
			outTxt(HttpResponseStatus.NOT_FOUND,"no image!");
		}
	}
}
